/*
 ============================================================================
 Name        : main.cpp
 Author      : mattia
 Version     :
 Copyright   : Your copyright notice
 Description : Compute Pi in MPI C++
 ============================================================================
 */

#include <stdio.h>
#include <mpi.h>
#include <pthread.h>
#include <time.h>
#include <ctime>
#include <string>
#include "DppmParams.h"
#include "Utils.h"
#include "TuplesProcessing.h"
#include "Tuple.h"
#include "FirstBranch.h"

using namespace std;

//Variables globales compartidas entre los threads
int rank, size;
bool end = false;
DppmParams * dppm;
Tuple * bestSolution;
Tuple * currentTuple;
int * lastTupleModes;
long * C;
int * minCosts;
int * minTimes;

int main(int argc, char ** argv) {

	int numAct, T;
	int* numPreds;
	int* numModes;
	int** Preds;
	int** ModesCosts;
	int** ModesTimes;

	//Inicializar MPI
//	int threadSupportProvided;

	MPI_Init(&argc , &argv );

	//Inicializar variables globales
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	MPI_Comm_size(MPI_COMM_WORLD, &size);
	debugPrinter(0, rank, "main", 51, "Inicializando MPI...","size",size);

	//El master lee la especificacion del DPPM desde el arhivo pasado por parametro
	if (!rank) {
		if ((argc != 2)
				|| readDppmSpec(argv[1], numAct, T, numPreds, Preds, numModes,
						ModesCosts, ModesTimes)) {
			printf("Error!!\n");
			return -1;
		}
		debugPrinter(0, rank, "main", 70, "Leido DppmParams info");
	}

	//Comunica la especificacion
	MPI_Bcast(&numAct, 1, MPI_INT, MASTER_RANK, MPI_COMM_WORLD);
	MPI_Bcast(&T, 1, MPI_INT, MASTER_RANK, MPI_COMM_WORLD);

	if (rank) {
		numPreds = new int[numAct];
		numModes = new int[numAct];
		Preds = new int*[numAct];
		ModesCosts = new int*[numAct];
		ModesTimes = new int*[numAct];
	}

	MPI_Bcast(numPreds, numAct, MPI_INT, MASTER_RANK, MPI_COMM_WORLD);
	MPI_Bcast(numModes, numAct, MPI_INT, MASTER_RANK, MPI_COMM_WORLD);

	for (int i = 0; i < numAct; i++) {
		if (rank) {
			Preds[i] = new int[numPreds[i]];
			ModesCosts[i] = new int[numModes[i]];
			ModesTimes[i] = new int[numModes[i]];
		}
		MPI_Bcast(Preds[i], numPreds[i], MPI_INT, MASTER_RANK, MPI_COMM_WORLD);
		MPI_Bcast(ModesCosts[i], numModes[i], MPI_INT, MASTER_RANK,
				MPI_COMM_WORLD);
		MPI_Bcast(ModesTimes[i], numModes[i], MPI_INT, MASTER_RANK,
				MPI_COMM_WORLD);
	}

	//Crear la variable dppm
	dppm = new DppmParams(numAct, T, numPreds, Preds, numModes, ModesCosts,	ModesTimes);

	//Inicializar el tamanio de las tuplas
	Tuple::SetTuplesIntSize(numAct);

	//Inicializar los vectores de poda
	minCosts = new int[numAct];
	minTimes = new int[numAct];
	defineBoundVectors(dppm, minCosts, minTimes);

	//Crear la primera bestSolution vacia
	bestSolution = new Tuple();

	//Procedimiento que selecciona equitativamenteel rango inicial del arbol en base a rank, size
	//y la especificacion del caso DPPM (numero de actividades y numeros de modos por actividad).
	if (rank) {

		FirstBranch();

		/***********MEDICION DEL TIEMPO OBTENIDO**************/
		time_t tic = time(0);
		string aux("INICIO PROCESSING!  At ");
		aux.append(ctime(&tic));
		debugPrinter(0, rank, "main", 198,
				aux.substr(0, aux.length() - 1).c_str());
		/*****************************************************/

		int iter = TuplesProcessing();

		/***********MEDICION DEL TIEMPO OBTENIDO**************/
		time_t toc = time(0);
		aux = string("FIN PROCESSING!  At ");
		aux.append(ctime(&toc));
		int timeElapsed = toc - tic;
		debugPrinter(0, rank, "main", 198,
				aux.substr(0, aux.length() - 1).c_str(),
				"Tiempo utilizado (en segundos)", timeElapsed,
				"Iteraciones:",iter);
		/*****************************************************/

	} else {

//		int debug = 1;
//		while (debug);

		int endedCount = size - 1; //number of slaves
		int endedProcess[size - 1];
		int whoFindBestSol = 0;
		memset(endedProcess,0, sizeof(int) * (size-1) );
		int msg;
		int tag;
		int cand[Tuple::tuplesIntSize];
		MPI_Status status;

		while (endedCount) {
			//cout<<"Master: "<<155<<endl;
			MPI_Recv(&msg, 1, MPI_INT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
			//cout<<"Master: "<<157<<endl;
			switch (status.MPI_TAG) {

			case BEST_SOLUTION_UPDATE:

				//cout<<"Master: "<<163<<endl;
				MPI_Recv(cand, Tuple::tuplesIntSize, MPI_INT, status.MPI_SOURCE,status.MPI_TAG, MPI_COMM_WORLD, &status);
				//cout<<"Master: "<<164<<endl;
				if (evalSolution(cand)) {
					whoFindBestSol = status.MPI_SOURCE;
					for (int i = 1; i < size; i++) {
						if( (! endedProcess[i - 1] ) && (i != status.MPI_SOURCE)){
							//cout<<"Master: "<<169<<endl;
							MPI_Send(&msg, 1, MPI_INT, i, BEST_SOLUTION_UPDATE, MPI_COMM_WORLD);
							MPI_Send(cand, Tuple::tuplesIntSize, MPI_INT, i, BEST_SOLUTION_UPDATE, MPI_COMM_WORLD);
							//cout<<"Master: "<<171<<endl;

						}
					}
//					cout<<"Master: "<<175<<endl;
//					MPI_Bcast(cand, Tuple::tuplesIntSize, MPI_INT, MASTER_RANK, MPI_COMM_WORLD);
//					cout<<"Master: "<<177<<endl;
				}
				break;

			case PROCESS_END:

				//cout<<"Master: "<<183<<endl;
				MPI_Recv(cand, Tuple::tuplesIntSize, MPI_INT, status.MPI_SOURCE, status.MPI_TAG, MPI_COMM_WORLD, &status);
				//cout<<"Master: "<<185<<endl;
				endedCount--;
				endedProcess[status.MPI_SOURCE - 1] = 1;
				if (evalSolution(cand)) {

					whoFindBestSol = status.MPI_SOURCE;

					if (endedCount > 0) {

						for (int i = 1; i < size; i++) {

							if( (! endedProcess[i - 1] ) && (i != status.MPI_SOURCE)){
								//cout<<"Master: "<<193<<endl;
								MPI_Send(&msg, 1, MPI_INT, i, BEST_SOLUTION_UPDATE, MPI_COMM_WORLD);
								MPI_Send(cand, Tuple::tuplesIntSize, MPI_INT, i, BEST_SOLUTION_UPDATE, MPI_COMM_WORLD);
								//cout<<"Master: "<<195<<endl;
							}
						}
					}
				}
//					cout<<"Master: "<<199<<endl;
//					MPI_Bcast(cand, Tuple::tuplesIntSize, MPI_INT, MASTER_RANK, MPI_COMM_WORLD);
//					cout<<"Master: "<<201<<endl;
				break;
			}

		}

		debugPrinter(0, rank, "main", 176, "La mejor solución: ","Slave", whoFindBestSol , "Costo", bestSolution->cost(),"Tiempo",bestSolution->partialTimes(numAct-1));
		printf("\n\nLa mejor solución es: \n");
		bestSolution->print();

	}

	MPI_Finalize();

	delete [] numPreds;
	delete [] Preds;
	delete [] numModes;
	delete [] ModesCosts;
	delete [] ModesTimes;
	delete dppm;
	delete bestSolution;
	delete[] C;
	delete[] lastTupleModes;
	delete currentTuple;
	delete minTimes;
	delete minCosts;

	return 0;
}

